import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DragGestureEvent;
import java.awt.dnd.DragGestureListener;
import java.awt.dnd.DragSource;
import java.awt.dnd.DragSourceDragEvent;
import java.awt.dnd.DragSourceDropEvent;
import java.awt.dnd.DragSourceEvent;
import java.awt.dnd.DragSourceListener;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.StringTokenizer;

import javax.swing.ButtonGroup;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.border.BevelBorder;
import javax.swing.border.Border;

public class ScribbleDragAndDrop extends JComponent implements
		DragGestureListener, // For recognizing the start of drags
		DragSourceListener, // For processing drag source events
		DropTargetListener, // For processing drop target events
		MouseListener, // For processing mouse clicks
		MouseMotionListener // For processing mouse drags
{
	ArrayList scribbles = new ArrayList(); // A list of Scribbles to draw

	Scribble currentScribble; // The scribble in progress

	Scribble beingDragged; // The scribble being dragged

	DragSource dragSource; // A central DnD object

	boolean dragMode; // Are we dragging or scribbling?

	// These are some constants we use
	static final int LINEWIDTH = 3;

	static final BasicStroke linestyle = new BasicStroke(LINEWIDTH);

	static final Border normalBorder = new BevelBorder(BevelBorder.LOWERED);

	static final Border dropBorder = new BevelBorder(BevelBorder.RAISED);

	/** */
	/** The constructor: set up drag-and-drop stuff */
	public ScribbleDragAndDrop() {
		// Give ourselves a nice default border.
		// We'll change this border during drag-and-drop.
		setBorder(normalBorder);

		// Register listeners to handle drawing
		addMouseListener(this);
		addMouseMotionListener(this);

		// Create a DragSource and DragGestureRecognizer to listen for drags
		// The DragGestureRecognizer will notify the DragGestureListener
		// when the user tries to drag an object
		dragSource = DragSource.getDefaultDragSource();
		dragSource.createDefaultDragGestureRecognizer(this, // What component
				DnDConstants.ACTION_COPY_OR_MOVE, // What drag types?
				this);// the listener

		DropTarget dropTarget = new DropTarget(this, // component to monitor
				this); // listener to notify
		this.setDropTarget(dropTarget); // Tell the component about it.
	}

	/** */
	/**
	 * The component draws itself by drawing each of the Scribble objects.
	 */
	public void paintComponent(Graphics g) {
		super.paintComponent(g);
		Graphics2D g2 = (Graphics2D) g;
		g2.setStroke(linestyle); // Specify wide lines

		int numScribbles = scribbles.size();
		for (int i = 0; i < numScribbles; i++) {
			Scribble s = (Scribble) scribbles.get(i);
			g2.draw(s); // Draw the scribble
		}
	}

	public void setDragMode(boolean dragMode) {
		this.dragMode = dragMode;
	}

	public boolean getDragMode() {
		return dragMode;
	}

	/** */
	/**
	 * This method, and the following four methods are from the MouseListener
	 * interface. If we're in drawing mode, this method handles mouse down
	 * events and starts a new scribble.
	 */
	public void mousePressed(MouseEvent e) {
		if (dragMode)
			return;
		currentScribble = new Scribble();
		scribbles.add(currentScribble);
		currentScribble.moveto(e.getX(), e.getY());
	}

	public void mouseReleased(MouseEvent e) {
	}

	public void mouseClicked(MouseEvent e) {
	}

	public void mouseEntered(MouseEvent e) {
	}

	public void mouseExited(MouseEvent e) {
	}

	public void mouseDragged(MouseEvent e) {
		if (dragMode)
			return;
		currentScribble.lineto(e.getX(), e.getY());
		repaint();
	}

	public void mouseMoved(MouseEvent e) {
	}

	public void dragGestureRecognized(DragGestureEvent e) {
		// Don't drag if we're not in drag mode
		if (!dragMode)
			return;

		// Figure out where the drag started
		MouseEvent inputEvent = (MouseEvent) e.getTriggerEvent();
		int x = inputEvent.getX();
		int y = inputEvent.getY();

		// Figure out which scribble was clicked on, if any by creating a
		// small rectangle around the point and testing for intersection.
		Rectangle r = new Rectangle(x - LINEWIDTH, y - LINEWIDTH,LINEWIDTH * 2, LINEWIDTH * 2);
		int numScribbles = scribbles.size();
		for (int i = 0; i < numScribbles; i++) { // Loop through the
			// scribbles
			Scribble s = (Scribble) scribbles.get(i);
			if (s.intersects(r)) {
				// The user started the drag on top of this scribble, so
				// start to drag it.

				// First, remember which scribble is being dragged, so we can
				// delete it later (if this is a move rather than a copy)
				beingDragged = s;

				// Next, create a copy that will be the one dragged
				Scribble dragScribble = (Scribble) s.clone();
				// Adjust the origin to the point the user clicked on.
				dragScribble.translate(-x, -y);

				// Choose a cursor based on the type of drag the user initiated
				Cursor cursor;
				switch (e.getDragAction()) {
				case DnDConstants.ACTION_COPY:
					cursor = DragSource.DefaultCopyDrop;
					break;
				case DnDConstants.ACTION_MOVE:
					cursor = DragSource.DefaultMoveDrop;
					break;
				default:
					return; // We only support move and copys
				}

				// Some systems allow us to drag an image along with the
				// cursor. If so, create an image of the scribble to drag
				if (DragSource.isDragImageSupported()) {
					Rectangle scribbleBox = dragScribble.getBounds();
					Image dragImage = this.createImage(scribbleBox.width,scribbleBox.height);
					Graphics2D g = (Graphics2D) dragImage.getGraphics();
					g.setColor(new Color(0, 0, 0, 0)); // transparent
					// background
					g.fillRect(0, 0, scribbleBox.width, scribbleBox.height);
					g.setColor(Color.RED);
					g.setStroke(linestyle);
					g.translate(-scribbleBox.x, -scribbleBox.y);
					g.draw(dragScribble);
					Point hotspot = new Point(-scribbleBox.x, -scribbleBox.y);

					// Now start dragging, using the image.
					e.startDrag(cursor, dragImage, hotspot, dragScribble, this);
				} else {
					// Or start the drag without an image
					e.startDrag(cursor, dragScribble, this);
				}
				// After we've started dragging one scribble, stop looking
				return;
			}
		}
	}

	/** */
	/**
	 * This method, and the four unused methods that follow it implement the
	 * DragSourceListener interface. dragDropEnd() is invoked when the user
	 * drops the scribble she was dragging. If the drop was successful, and if
	 * the user did a "move" rather than a "copy", then we delete the dragged
	 * scribble from the list of scribbles to draw.
	 */
	public void dragDropEnd(DragSourceDropEvent e) {
		if (!e.getDropSuccess())
			return;
		int action = e.getDropAction();
		if (action == DnDConstants.ACTION_MOVE) {
			scribbles.remove(beingDragged);
			beingDragged = null;
			repaint();
		}
	}

	// These methods are also part of DragSourceListener.
	// They are invoked at interesting points during the drag, and can be
	// used to perform "drag over" effects, such as changing the drag cursor
	// or drag image.
	public void dragEnter(DragSourceDragEvent e) {
	}

	public void dragExit(DragSourceEvent e) {
	}

	public void dropActionChanged(DragSourceDragEvent e) {
	}

	public void dragOver(DragSourceDragEvent e) {
	}

	// The next five methods implement DropTargetListener

	/** */
	/**
	 * This method is invoked when the user first drags something over us. If we
	 * understand the data type being dragged, then call acceptDrag() to tell
	 * the system that we're receptive. Also, we change our border as a "drag
	 * under" effect to signal that we can accept the drop.
	 */
	public void dragEnter(DropTargetDragEvent e) {
		if (e.isDataFlavorSupported(Scribble.scribbleDataFlavor)
				|| e.isDataFlavorSupported(DataFlavor.stringFlavor)) {
			e.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);
			this.setBorder(dropBorder);
		}
	}

	/** */
	/** The user is no longer dragging over us, so restore the border */
	public void dragExit(DropTargetEvent e) {
		this.setBorder(normalBorder);
	}

	/** */
	/**
	 * This is the key method of DropTargetListener. It is invoked when the user
	 * drops something on us.
	 */
	public void drop(DropTargetDropEvent e) {
		this.setBorder(normalBorder); // Restore the default border

		// First, check whether we understand the data that was dropped.
		// If we supports our data flavors, accept the drop, otherwise reject.
		if (e.isDataFlavorSupported(Scribble.scribbleDataFlavor)
				|| e.isDataFlavorSupported(DataFlavor.stringFlavor)) {
			e.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
		} else {
			e.rejectDrop();
			return;
		}

		// We've accepted the drop, so now we attempt to get the dropped data
		// from the Transferable object.
		Transferable t = e.getTransferable(); // Holds the dropped data
		Scribble droppedScribble; // This will hold the Scribble object

		// First, try to get the data directly as a scribble object
		try {
			droppedScribble = (Scribble) t
					.getTransferData(Scribble.scribbleDataFlavor);
		} catch (Exception ex) { // unsupported flavor, IO exception, etc.
			// If that doesn't work, try to get it as a String and parse it
			try {
				String s = (String) t.getTransferData(DataFlavor.stringFlavor);
				droppedScribble = Scribble.parse(s);
			} catch (Exception ex2) {
				// If we still couldn't get the data, tell the system we failed
				e.dropComplete(false);
				return;
			}
		}

		// If we get here, we've got the Scribble object
		Point p = e.getLocation(); // Where did the drop happen?
		droppedScribble.translate(p.getX(), p.getY()); // Move it there
		scribbles.add(droppedScribble); // add to display list
		repaint(); // ask for redraw
		e.dropComplete(true); // signal success!
	}

	// These are unused DropTargetListener methods
	public void dragOver(DropTargetDragEvent e) {
	}

	public void dropActionChanged(DropTargetDragEvent e) {
	}

	/** */
	/**
	 * The main method. Creates a simple application using this class. Note the
	 * buttons for switching between draw mode and drag mode.
	 */
	public static void main(String[] args) {
		// Create a frame and put a scribble pane in it
		JFrame frame = new JFrame("ScribbleDragAndDrop");
		final ScribbleDragAndDrop scribblePane = new ScribbleDragAndDrop();
		frame.getContentPane().add(scribblePane, BorderLayout.CENTER);

		// Create two buttons for switching modes
		JToolBar toolbar = new JToolBar();
		ButtonGroup group = new ButtonGroup();
		JToggleButton draw = new JToggleButton("Draw");
		JToggleButton drag = new JToggleButton("Drag");
		draw.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				scribblePane.setDragMode(false);
			}
		});
		drag.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				scribblePane.setDragMode(true);
			}
		});
		group.add(draw);
		group.add(drag);
		toolbar.add(draw);
		toolbar.add(drag);
		frame.getContentPane().add(toolbar, BorderLayout.NORTH);

		// Start off in drawing mode
		draw.setSelected(true);
		scribblePane.setDragMode(false);

		// Pop up the window
		frame.setSize(400, 400);
		frame.setVisible(true);
	}
}

class Scribble implements Shape, Transferable, Serializable, Cloneable {
	protected double[] points = new double[64]; // The scribble data

	protected int numPoints = 0; // The current number of points

	double maxX = Double.NEGATIVE_INFINITY; // The bounding box

	double maxY = Double.NEGATIVE_INFINITY;

	double minX = Double.POSITIVE_INFINITY;

	double minY = Double.POSITIVE_INFINITY;

	/** */
	/**
	 * Begin a new polyline at (x,y). Note the use of Double.NaN in the points
	 * array to mark the beginning of a new polyline
	 */
	public void moveto(double x, double y) {
		if (numPoints + 3 > points.length)
			reallocate();
		// Mark this as the beginning of a new line
		points[numPoints++] = Double.NaN;
		// The rest of this method is just like lineto();
		lineto(x, y);
	}

	/** */
	/**
	 * Add the point (x,y) to the end of the current polyline
	 */
	public void lineto(double x, double y) {
		if (numPoints + 2 > points.length)
			reallocate();
		points[numPoints++] = x;
		points[numPoints++] = y;

		// See if the point enlarges our bounding box
		if (x > maxX)
			maxX = x;
		if (x < minX)
			minX = x;
		if (y > maxY)
			maxY = y;
		if (y < minY)
			minY = y;
	}

	/** */
	/**
	 * Append the Scribble s to this Scribble
	 */
	public void append(Scribble s) {
		int n = numPoints + s.numPoints;
		double[] newpoints = new double[n];
		System.arraycopy(points, 0, newpoints, 0, numPoints);
		System.arraycopy(s.points, 0, newpoints, numPoints, s.numPoints);
		points = newpoints;
		numPoints = n;
		minX = Math.min(minX, s.minX);
		maxX = Math.max(maxX, s.maxX);
		minY = Math.min(minY, s.minY);
		maxY = Math.max(maxY, s.maxY);
	}

	/** */
	/**
	 * Translate the coordinates of all points in the Scribble by x,y
	 */
	public void translate(double x, double y) {
		for (int i = 0; i < numPoints; i++) {
			if (Double.isNaN(points[i]))
				continue;
			points[i++] += x;
			points[i] += y;
		}
		minX += x;
		maxX += x;
		minY += y;
		maxY += y;
	}

	/** */
	/** An internal method to make more room in the data array */
	protected void reallocate() {
		double[] newpoints = new double[points.length * 2];
		System.arraycopy(points, 0, newpoints, 0, numPoints);
		points = newpoints;
	}

	/** */
	/** Clone a Scribble object and its internal array of data */
	public Object clone() {
		try {
			Scribble s = (Scribble) super.clone(); // make a copy of all fields
			s.points = (double[]) points.clone(); // copy the entire array
			return s;
		} catch (CloneNotSupportedException e) { // This should never happen
			return this;
		}
	}

	/** */
	/** Convert the scribble data to a textual format */
	public String toString() {
		StringBuffer b = new StringBuffer();
		for (int i = 0; i < numPoints; i++) {
			if (Double.isNaN(points[i])) {
				b.append("m ");
			} else {
				b.append(points[i]);
				b.append(' ');
			}
		}
		return b.toString();
	}

	/** */
	/**
	 * Create a new Scribble object and initialize it by parsing a string of
	 * coordinate data in the format produced by toString()
	 */
	public static Scribble parse(String s) throws NumberFormatException {
		StringTokenizer st = new StringTokenizer(s);
		Scribble scribble = new Scribble();
		while (st.hasMoreTokens()) {
			String t = st.nextToken();
			if (t.charAt(0) == 'm') {
				scribble.moveto(Double.parseDouble(st.nextToken()), Double
						.parseDouble(st.nextToken()));
			} else {
				scribble.lineto(Double.parseDouble(t), Double.parseDouble(st
						.nextToken()));
			}
		}
		return scribble;
	}

	// ========= The following methods implement the Shape interface ========

	/** */
	/** Return the bounding box of the Shape */
	public Rectangle getBounds() {
		return new Rectangle((int) (minX - 0.5f), (int) (minY - 0.5f),
				(int) (maxX - minX + 0.5f), (int) (maxY - minY + 0.5f));
	}

	/** */
	/** Return the bounding box of the Shape */
	public Rectangle2D getBounds2D() {
		return new Rectangle2D.Double(minX, minY, maxX - minX, maxY - minY);
	}

	/** */
	/** Our shape is an open curve, so it never contains anything */
	public boolean contains(Point2D p) {
		return false;
	}

	public boolean contains(Rectangle2D r) {
		return false;
	}

	public boolean contains(double x, double y) {
		return false;
	}

	public boolean contains(double x, double y, double w, double h) {
		return false;
	}

	/** */
	/**
	 * Determine if the scribble intersects the specified rectangle by testing
	 * each line segment individually
	 */
	public boolean intersects(Rectangle2D r) {
		if (numPoints < 4)
			return false;
		int i = 0;
		double x1, y1, x2 = 0.0, y2 = 0.0;
		while (i < numPoints) {
			if (Double.isNaN(points[i])) { // If we're beginning a new line
				i++; // Skip the NaN
				x2 = points[i++];
				y2 = points[i++];
			} else {
				x1 = x2;
				y1 = y2;
				x2 = points[i++];
				y2 = points[i++];
				if (r.intersectsLine(x1, y1, x2, y2))
					return true;
			}
		}

		return false;
	}

	/** */
	/** Test for intersection by invoking the method above */
	public boolean intersects(double x, double y, double w, double h) {
		return intersects(new Rectangle2D.Double(x, y, w, h));
	}

	/** */
	/**
	 * Return a PathIterator object that tells Java2D how to draw this scribble
	 */
	public PathIterator getPathIterator(AffineTransform at) {
		return new ScribbleIterator(at);
	}

	/** */
	/**
	 * Return a PathIterator that doesn't include curves. Ours never does.
	 */
	public PathIterator getPathIterator(AffineTransform at, double flatness) {
		return getPathIterator(at);
	}

	/** */
	/**
	 * This inner class implements the PathIterator interface to describe the
	 * shape of a scribble. Since a Scribble is composed of arbitrary movetos
	 * and linetos, we simply return their coordinates
	 */
	public class ScribbleIterator implements PathIterator {
		protected int i = 0; // Position in array

		protected AffineTransform transform;

		public ScribbleIterator(AffineTransform transform) {
			this.transform = transform;
		}

		/** */
		/** How to determine insideness and outsideness for this shape */
		public int getWindingRule() {
			return PathIterator.WIND_NON_ZERO;
		}

		/** */
		/** Have we reached the end of the scribble path yet? */
		public boolean isDone() {
			return i >= numPoints;
		}

		/** */
		/** Move on to the next segment of the path */
		public void next() {
			if (Double.isNaN(points[i]))
				i += 3;
			else
				i += 2;
		}

		/** */
		/**
		 * Get the coordinates of the current moveto or lineto as floats
		 */
		public int currentSegment(float[] coords) {
			int retval;
			if (Double.isNaN(points[i])) { // If its a moveto
				coords[0] = (float) points[i + 1];
				coords[1] = (float) points[i + 2];
				retval = SEG_MOVETO;
			} else {
				coords[0] = (float) points[i];
				coords[1] = (float) points[i + 1];
				retval = SEG_LINETO;
			}

			// If a transform was specified, use it on the coordinates
			if (transform != null)
				transform.transform(coords, 0, coords, 0, 1);

			return retval;
		}

		/** */
		/**
		 * Get the coordinates of the current moveto or lineto as doubles
		 */
		public int currentSegment(double[] coords) {
			int retval;
			if (Double.isNaN(points[i])) {
				coords[0] = points[i + 1];
				coords[1] = points[i + 2];
				retval = SEG_MOVETO;
			} else {
				coords[0] = points[i];
				coords[1] = points[i + 1];
				retval = SEG_LINETO;
			}
			if (transform != null)
				transform.transform(coords, 0, coords, 0, 1);
			return retval;
		}
	}

	// ====== The following methods implement the Transferable interface =====

	// This is the custom DataFlavor for Scribble objects
	public static DataFlavor scribbleDataFlavor = new DataFlavor(
			Scribble.class, "Scribble");

	// This is a list of the flavors we know how to work with
	public static DataFlavor[] supportedFlavors = { scribbleDataFlavor,
			DataFlavor.stringFlavor };

	/** */
	/** Return the data formats or "flavors" we know how to transfer */
	public DataFlavor[] getTransferDataFlavors() {
		return (DataFlavor[]) supportedFlavors.clone();
	}

	/** */
	/** Check whether we support a given flavor */
	public boolean isDataFlavorSupported(DataFlavor flavor) {
		return (flavor.equals(scribbleDataFlavor) || flavor
				.equals(DataFlavor.stringFlavor));
	}

	/** */
	/**
	 * Return the scribble data in the requested format, or throw an exception
	 * if we don't support the requested format
	 */
	public Object getTransferData(DataFlavor flavor)
			throws UnsupportedFlavorException {
		if (flavor.equals(scribbleDataFlavor)) {
			return this;
		} else if (flavor.equals(DataFlavor.stringFlavor)) {
			return toString();
		} else
			throw new UnsupportedFlavorException(flavor);
	}
}